package com.im.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.im.pojo.SocketMsg;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * websocket的具体实现类
 * 使用springboot的唯一区别是要 @Component声明下，而使用独立容器是有容器自己管理WebSocket的，
 * 但是在springboot中连容器都是spring管理的。
 * 虽然@Component默认是单利模式的，但是springboot还是会为每个websocket连接初始化一个bean,
 * 所以可以用一个静态set保存起来
 */
@ServerEndpoint(value = "/websocket/{nickname}")
@Component
public class MyWebSocket {

    private String nickName;

    // 用来保存每个客户端对应的MyWebSocket对象
    private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<>();

    // 用来记录sessionid和该sessionid进行绑定
    private static Map<String,Session> map = new HashMap<>();

    // 与客户端的链接会话，需要通过它来个客户端发送数据
    private Session session;

    private static final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 建立链接成功调用的方法
     * @param session
     */
    @OnOpen
    public void onOpen(Session session,@PathParam("nickname")String nickName) throws JsonProcessingException {
        this.session = session;
        this.nickName = nickName;
        // 添加用户
        map.put(session.getId(),session);
        // 加入set中
        webSocketSet.add(this);
        System.out.println("有新连接加入："+nickName+",当前在线人数为"+webSocketSet.size());
        // 创建当前用户对象
        SocketMsg socketMsg = new SocketMsg();
        socketMsg.setFormUser(nickName);
        socketMsg.setSessionId(session.getId());
        // 群发消息
       // broadcast("恭喜"+nickName+"您成功链接上WebSocket(其频道号为："+session.getId()+")-->当前在线人数为："+webSocketSet.size());
        // 将用户对象返回
        broadcast(objectMapper.writeValueAsString(socketMsg));
    }

    /**
     * 链接关闭调用的方法
     */
    @OnClose
    public void onClose(){
        // 从set中删除
        webSocketSet.remove(this);
        System.out.println("有一个链接关闭！当前在线人数为："+webSocketSet.size());
    }

    /**
     * 收到客户端消息后调用的方法
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message,Session session,@PathParam("nickname")String nickName) throws IOException {
        System.out.println("来自客户端的消息："+nickName+":"+message);
        // 从客户端传过来的数据是json数据，所以这里使用jackson进行转换为socketmsg对象
        // 然后通过socketmsg的type进行判断是单聊还是群聊，进行相应的处理
        SocketMsg socketMsg = null;
        socketMsg = objectMapper.readValue(message,SocketMsg.class);
        if(socketMsg.getType() == 1){
            // 单聊，需要找到发送者和接收者
            socketMsg.setFormUser(session.getId());// 发送者
            Session formSession = map.get(socketMsg.getFormUser());
            Session toSessin = map.get(socketMsg.getToUser());
            // 发送给接收者
            if(toSessin != null){
                // 发送给发送者
                formSession.getAsyncRemote().sendText(nickName+"："+socketMsg.getMsg());
                // 发送给接收者
                toSessin.getAsyncRemote().sendText(nickName+"："+socketMsg.getMsg());
            }else{
                // 发送给发送者
                formSession.getAsyncRemote().sendText("系统消息：对方不在线或者您输入的频道号不对");
            }
        }else {
            // 群发消息
            broadcast(nickName+":"+socketMsg.getMsg());
        }
    }

    /**
     * 发生错误时调用
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session,Throwable error){
        System.out.println("发生错误！");
        error.printStackTrace();
    }

    /**
     * 群发自定义消息
     * @param message
     */
    public void broadcast(String message){
        webSocketSet.forEach(item ->{
            item.session.getAsyncRemote().sendText(message);
        });
    }
}
